const fs = require('fs')
const path = require('path')
const archiver = require('archiver')
const { execSync } = require('child_process')
const { program } = require('commander')
const { distPath } = require('./config')
const { formatFileSize, hasOwnProperty } = require('./utils/common')
const { mkdirIfNotExist, clearDir } = require('./utils/fs')
const { getPackList } = require('./utils/pack')

program.option('-m, --mode <name>', 'publish mode (default, zip)', 'default')

program.parse(process.argv)

const mode = program.opts().mode
const modes = ['default', 'zip', 'dir']

if (!modes.includes(mode)) {
  throw new Error(`Invalid mode: ${mode}`)
}

const packageJsonPath = path.join(__dirname, '../package.json')
const backupPath = path.join(__dirname, '../dist/package.backup.json')
const rawContent = fs.readFileSync(packageJsonPath, 'utf-8')
const parseContent = JSON.parse(rawContent)
let canRestore = true
let isRestore = false

fs.copyFileSync(packageJsonPath, backupPath)

async function zipMode() {
  const pathList = await getPackList(path.dirname(packageJsonPath))
  mkdirIfNotExist(distPath)
  const outputPath = path.join(distPath, 'pub.zip')
  const archive = archiver('zip', { zlib: { level: 9 } })
  // 创建输出流
  const output = fs.createWriteStream(outputPath)
  // 当打包完成时，关闭输出流
  output.on('close', () => {
    canRestore = true
    console.log('打包完成')
    console.log('path:', outputPath)
    console.log('size:', formatFileSize(archive.pointer()))
  })

  // 当发生错误时，输出错误信息
  archive.on('error', (err) => {
    if (!isRestore) {
      restorePackageJson()
    }
    throw err
  })

  // 将输出流管道到ZipArchive实例中
  archive.pipe(output)

  const readPath = process.cwd()

  pathList.forEach((file) => {
    archive.file(path.join(readPath, file), { name: file })
  })

  // 完成打包操作
  archive.finalize()
}

async function dirMode() {
  const packageDir = path.dirname(packageJsonPath)
  const pathList = await getPackList(packageDir)
  const outputPath = path.join(distPath, 'dir')
  mkdirIfNotExist(outputPath)
  clearDir(outputPath)
  pathList.forEach((file) => {
    const filePath = path.join(packageDir, file)
    const dest = path.join(outputPath, file)
    const dirPath = path.dirname(dest)
    if (!fs.existsSync(dirPath)) fs.mkdirSync(dirPath, { recursive: true })
    fs.copyFileSync(filePath, dest)
  })
  canRestore = true
  console.log('打包完成')
  console.log('path:', outputPath)
}

function defaultMode() {
  execSync('npm publish')
}

try {
  writeNewPackageJson()

  if (mode === 'zip') {
    canRestore = false
    zipMode()
  }
  if (mode === 'dir') {
    canRestore = false
    dirMode()
  } else {
    defaultMode()
  }
} catch (error) {
  console.error(error)
} finally {
  const _restore = () => {
    if (canRestore && !isRestore) {
      restorePackageJson()
    }
    if (!isRestore) {
      setTimeout(_restore, 100)
    }
  }
  _restore()
}

// 写入新的 package.json
function writeNewPackageJson() {
  const reservePropertyList = [
    'name',
    'version',
    'keywords',
    'homepage',
    'repository',
    'license',
    'private',
    'description',
    'main',
    'types',
    'engines',
    'bin',
    'files'
  ]
  const newPackageJson = reservePropertyList.reduce((obj, property) => {
    if (!hasOwnProperty(parseContent, property)) return obj
    if (hasOwnProperty(obj, property)) return obj
    if (property === 'bin') {
      const bin = parseContent[property]
      Object.keys(bin).forEach((key) => {
        if (key.startsWith('_')) {
          // 去除临时命令
          delete bin[key]
        }
      })
      obj[property] = bin
    } else {
      obj[property] = parseContent[property]
    }
    return obj
  }, {})
  fs.writeFileSync(packageJsonPath, JSON.stringify(newPackageJson, null, 2), 'utf-8')
}

// 还原 package.json
function restorePackageJson() {
  if (isRestore) return
  isRestore = true
  fs.writeFileSync(packageJsonPath, rawContent, 'utf-8')
}
